#include"HF_Defines.h"
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "RMM.h"
#include "BF_Lib.h"
#include "BF_Defines.h"

/* Global μεταβλητές που κρατάνε πόσες εγγραφές έχει
 *το κάθε block και ποιό το μέγεθος του bitmap του
 *block.
 */

int recsPerBlock = 0;
int headerSize = 0;

void RMM_ComputeRecsPerBlock ( int recordSize );
BOOLEAN HF_HEADER_BlockIsFull ( int blockNum, char* header );
BOOLEAN RMM_RecIsValid ( int bit_position, char *block );
BOOLEAN RMM_BlockIsEmpty ( char *block );
int RMM_GetEmptyRecSpot ( char *block );
void RMM_BitmapSet ( int recPosition,char *block, BOOLEAN value );
void HF_HEADER_Set ( int blockNum,char *header, BOOLEAN dirty );

int RMM_InsertRec ( int fileDesc, char *record, int recordSize )
{
	char *header, *block;
	int blockNum = 0, headerNum;
	int recPosition = 0;
	BOOLEAN newBlock = FALSE;

	RMM_ComputeRecsPerBlock ( recordSize );
	
	/* Προσκόμηση του  HF Header στην ενδιάμεση μνήμη. */

	header = malloc ( sizeof( char ) * BF_BLOCK_SIZE );
	if ( ( HF_errno = BF_GetFirstBlock ( fileDesc, &headerNum, &header ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}
	
	/* Έλεγχος για το πρώτο μη γεμάτο block. Αν ολα τα block είναι
	 *γεμάτα γίνετε επιστροφή λάθους.
	 */

	while ( HF_HEADER_BlockIsFull ( blockNum, header ) )
	{		
		blockNum++;
		
		if ( blockNum == MAX_BLOCKS )
		{
			return HF_errno = HFE_EOF;
		}
	}
	
	/* Προσκόμηση του μη γεμάτου block που εντοπίσαμε προηγουμένως. 
	 *Αν το block δεν υπάρχει, το δημιουργούμε με την κλήση της BF_Alloc.
	 */
	
	block = malloc ( sizeof( char ) * BF_BLOCK_SIZE );
	if ( ( HF_errno = BF_GetThisBlock ( fileDesc, blockNum + 1, &block ) ) < 0 )
	{
		if ( HF_errno == BFE_INVALIDBLOCK )
		{		
			/* Αν το block δεν υπάρχει το δημιουργώ. */
			newBlock = TRUE;
			HF_errno = BF_AllocBlock ( fileDesc, &blockNum, &block );
			
			if ( HF_errno < 0 )
			{
				return HF_errno;
			}
		}
		else
		{
			HF_errno = HFE_BFERROR;
			return HF_errno;		
		}
	}
	
	/* Εύρεση της πρώτης άδειας εγγραφής μέσα στο block.
	 *Στη συνέχεια γίνετε αντιγραφή της δοσμένης εγγραφής
	 *στην θέση που βρήκαμε κενή και ανανεώνουμε το bitmap 
	 *του block ώστε να ξέρει πως γέμισε η κενή θέση.
	 */

	recPosition = RMM_GetEmptyRecSpot ( block );
	memcpy ( (void *) ( block + ( recPosition * recordSize ) ), (void *) record, recordSize );
	RMM_BitmapSet ( recPosition, block, TRUE );

	/* Αν με την εισαγωγή της καινούριας εγγραφής γέμισε το block,
	 *ενημερώνουμε τοω HF_Header, θετωντας το bit που αντιστοιχεί στο 
	 *block αυτό σε true. Στη συνέχεια κανουμε unpin το header και το block 
	 *απο την ενδιάμεση μνήμη.
	 */

	if ( RMM_GetEmptyRecSpot ( block ) == BLOCK_FULL )
	{
		HF_HEADER_Set ( blockNum, header, TRUE );

		if ( ( HF_errno = BF_UnpinBlock ( fileDesc, headerNum, TRUE ) ) < 0 )
		{			
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}
	}	
	else
	{
		if ( ( HF_errno = BF_UnpinBlock ( fileDesc, headerNum, FALSE ) ) < 0 )
		{			
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}
	}

	if ( newBlock == TRUE )
	{
		if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum, TRUE ) ) < 0 )
		{			
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}	
	}
	else
	{
		if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum + 1, TRUE ) ) < 0 )
		{			
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}
	}

	return blockNum * recsPerBlock + recPosition;
}


int RMM_DeleteRec ( int fileDesc, int recId, int recordSize )
{
	char *header, *block;
	int blockNum = 0 , headerNum;
	int recNum;
	BOOLEAN block_was_full = FALSE, block_is_empty = FALSE;
	
	RMM_ComputeRecsPerBlock ( recordSize );
	
	/*Βρίσκουμε το block που βρίσκετε η εγγραφή. Υπολογίζουμε τη
	 *θέση της εγγραφής μέσα στο block Και φέρνουμε το block 
	 *στην ενδιάμεση μνήμη.
	 */

	blockNum = recId / recsPerBlock;
	recNum = recId % recsPerBlock;
	
	block = malloc ( BF_BLOCK_SIZE * sizeof ( char ) );
	if ( ( HF_errno = BF_GetThisBlock ( fileDesc, blockNum + 1, &block ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}

	/* Κοιτάμε αν το block πριν την διαγραφή της εγγραφής ήταν γεμάτο,
	 *ώστε να ενημερώσουμε το header σε περίπτωησ που ήταν και άλλαξε
	 */

	if ( RMM_GetEmptyRecSpot ( block ) == BLOCK_FULL )
	{
		block_was_full = TRUE;
	}

	/* Αν η εγγραφή δεν είναι έγκυρη, επιστρφοφη λάθους. */

	if ( RMM_RecIsValid ( recNum, block ) == FALSE )
	{
		return HF_errno = HFE_INVALIDREC;
	}
	
	/* Διαγραφή της εγγραφής συμαδεύοντας την αντίστοιχη θέση στο bitmap. */
	RMM_BitmapSet ( recNum, block, FALSE );
	
	block_is_empty = RMM_BlockIsEmpty(block);	

	if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum + 1, TRUE ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}

	if ( block_is_empty )
	{
		/* Αν το block άδειασε το κάνουμε dispose.*/

		if ( ( HF_errno = BF_DisposeBlock ( fileDesc, blockNum + 1) ) < 0 )
		{
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}
	}

	if ( block_was_full )
	{
		/* Αν το block ήταν γεμάτο, μετα τη διαγραφεί της εγγραφής δε θα είναι πλέον.
		 *Επομένως φέρνουμε το HF header στην ενδιάμεση μνήμη, του ενημερώνουμε το bitmap
		 *ότι το blockNum block δεν ειναι γεματο πλεον και το κανουμε unpin απο την μνημη.
		 */

		header = malloc ( BF_BLOCK_SIZE * sizeof ( char ) );
		if ( ( HF_errno = BF_GetFirstBlock ( fileDesc, &headerNum, &header ) ) < 0 )
		{
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}

		HF_HEADER_Set ( blockNum, header, FALSE );

		if( ( HF_errno = BF_UnpinBlock ( fileDesc, headerNum, TRUE ) ) < 0 )
		{
			HF_errno = HFE_BFERROR;
			return HF_errno;
		}		
	}

	return HF_errno = HFE_OK;
}


int RMM_GetFirstRec ( int fileDesc, char *record, int recordSize )
{
	int headerNum, blockNum, recNum = 0;
	char *header, *block;

	RMM_ComputeRecsPerBlock ( recordSize );
	
	/* Προσκόμηση του HF header και του επόμενου block στην ενδιάμεση μνήμη. */

	header = malloc ( sizeof ( char ) * BF_BLOCK_SIZE );
	if ( ( HF_errno = BF_GetFirstBlock ( fileDesc, &headerNum, &header ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}

	blockNum = headerNum;
	
	block = malloc ( sizeof ( char ) * BF_BLOCK_SIZE );
	if ( ( HF_errno = BF_GetNextBlock ( fileDesc, &blockNum, &block ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}
	
	/* Εύρεση της πρώτης εγγραφής στο block.  */

	while ( RMM_RecIsValid ( recNum, block ) == FALSE && recNum < headerSize * 8 )
	{
		recNum++;
	}
	
	if ( recNum == headerSize * 8 )
	{
		return HF_errno = RMM_INVALID_BLOCK;
	}
	
	memcpy ( record, &block [ recNum * recordSize ], recordSize );

	/* Unpin το HF header και το block απο τη ενδιάμεση μνήμη. */

	if ( ( HF_errno = BF_UnpinBlock ( fileDesc, headerNum, FALSE ) ) < 0 )
	{		
		HF_errno = HFE_BFERROR;
		return HF_errno;														
	}

	if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum, FALSE) ) < 0 )
	{			
		HF_errno = HFE_BFERROR;
		return HF_errno;														
	}
	
	return recNum;
}


int RMM_GetNextRec ( int fileDesc, int recId, char *record, int recordSize )
{
	char *header, *block;
	int headerNum, blockNum, recNum;
	
	RMM_ComputeRecsPerBlock ( recordSize );
	
	recNum = ( recId + 1 ) % recsPerBlock;
	blockNum = ( recId + 1 ) / recsPerBlock;

	block = malloc ( sizeof ( char ) * BF_BLOCK_SIZE );
	if ( ( HF_errno = BF_GetThisBlock ( fileDesc, blockNum + 1, &block ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}
	
	/* Έλεγχος για να βρεθεί η επόμενη έγκυρη εγγραφή.*/

	while ( RMM_RecIsValid ( recNum, block ) == FALSE && recNum < recsPerBlock )
	{
		recNum++;
	}
	
	if ( recNum < recsPerBlock )
	{
		/* Αν η εγγραφη βρέθηκε. */

		memcpy ( record, &block [ recNum * recordSize ], recordSize );

		if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum + 1, FALSE ) ) < 0 )
		{
			HF_errno = HFE_BFERROR;
			return HF_errno;	
		}
	}
	else
	{
		if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum + 1, FALSE ) ) < 0 )
		{
			HF_errno = HFE_BFERROR;
			return HF_errno;	
		}

		HF_errno = HFE_EOF;
		return HF_errno;
	}


	return blockNum * recsPerBlock + recNum;
}

int RMM_GetThisRec ( int fileDesc, int recId, char *record, int recordSize )
{
	int blockNum, recNum;
	char *block;

	RMM_ComputeRecsPerBlock ( recordSize );

	blockNum = recId / recsPerBlock;
	recNum = recId % recsPerBlock;	
	
	block = malloc ( BF_BLOCK_SIZE * sizeof ( char ) );
	if ( ( HF_errno = BF_GetThisBlock ( fileDesc, blockNum + 1, &block ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}	
	
	/* Αν η εγγραφδή δεν είναι έγκυρη, επιστροφή λάθους. */

	if ( RMM_RecIsValid ( recNum, block ) == FALSE )
	{
		return HF_errno = HFE_INVALIDREC;
	}
	
	memcpy ( record, &block [ recNum * recordSize ], recordSize );

	if ( ( HF_errno = BF_UnpinBlock ( fileDesc, blockNum + 1, FALSE ) ) < 0 )
	{
		HF_errno = HFE_BFERROR;
		return HF_errno;
	}		
		
	HF_errno = HFE_OK;
	return HFE_OK;
}

//Utilities
void RMM_ComputeRecsPerBlock ( int recordSize )
{
	recsPerBlock = ( 8 * BF_BLOCK_SIZE ) / ( 8 * recordSize + 1 );
	headerSize = ( recsPerBlock + 1 ) / 8 ;
}

BOOLEAN HF_HEADER_BlockIsFull ( int blockNum, char *header )
{	
	int byte_position;
	int  bit_position = 0;
	int shifts;
	unsigned int bit_operator = 0 ,result;
	
	/*briskoume tin thesi tou byte kai tou bit stin kefalida kai dimiourgoume
	 mia stathera i opoia sto diadiko exei asso mono sto simio pou einai to 
	 sigekrimeno bit. diladi ean to bit enai stin thesi:
	 0 -> 1000 0000
	 1 -> 0100 0000
	 2 -> 0010 0000
	 ...
	 7 -> 0000 00001.
	 auto to kanw pernontas ton arithmo 0000 0001 kai kanontas analoges
	 olisthiseis pros ta aristera. Sigekrimena, 7 - bit position olisthiseis*/
	
	byte_position = blockNum / 8;	//gia na broume tin thesi tou byte 
	bit_position = blockNum % 8;	//gia na broume tin thesi tou bit
	shifts = 7 - bit_position;	//olisthiseis
	bit_operator = 1 << shifts;	//dimiourgia tis statheras
	
	result = bit_operator & header[byte_position];//logiko AND

	return result == 0 ? FALSE : TRUE;
}

BOOLEAN RMM_RecIsValid ( int bitPosition, char *block )
{			
	int bytePosition, shifts;
	unsigned int bit_operator, result;

	bytePosition = bitPosition / 8;
	shifts = bitPosition % 8;
	bit_operator = 1 << shifts;

	result = bit_operator & block[ BF_BLOCK_SIZE - bytePosition - 1 ];
	
	return result == 0 ? FALSE : TRUE;
}

BOOLEAN RMM_BlockIsEmpty ( char *block )
{
	int i;
	
	for ( i = 0; i < headerSize * 8; i++ )
	{
		if ( RMM_RecIsValid ( i, block ) == TRUE )
		{
			return FALSE;
		}
	}

	return TRUE;
}

int RMM_GetEmptyRecSpot ( char *block )
{
	int bit_position = 0;

	while ( bit_position < recsPerBlock )
	{	
		if ( RMM_RecIsValid ( bit_position, block ) == FALSE )
		{
			return bit_position;
		}
		
		bit_position++;
 	}
	
	return BLOCK_FULL;
}




void RMM_BitmapSet ( int recPosition, char *block, BOOLEAN value )
{
	char bitOperator = 0;
	int bitPosition = 0, bytePosition, shifts;
		
	bitPosition = recPosition;
	
	bytePosition = bitPosition / 8;
	shifts = bitPosition % 8;
	bitOperator = 1 << shifts;		
	
	if ( value == TRUE )
	{
		block [ BF_BLOCK_SIZE - bytePosition - 1 ] |= bitOperator;
	}
	else if ( value == FALSE )
	{
		bitOperator = ~bitOperator;
		block [ BF_BLOCK_SIZE - bytePosition - 1 ] &= bitOperator;
	}
}

void HF_HEADER_Set ( int blockNum, char *header, BOOLEAN full )
{
	char bit_position = 0, byte_position, shifts, bit_operator = 0 , result;
	
	bit_position = blockNum;

	byte_position = bit_position / 8;
	shifts = 7 - bit_position % 8;
	bit_operator = 1 << shifts;
	
	if ( full == TRUE )
	{
		header[ byte_position ] |= bit_operator;
	}
	else if ( full == FALSE )
	{
		bit_operator = ~bit_operator;
		header[ byte_position ] &= bit_operator;
	}
}



